/*globals grecaptcha*/
import Component from '../_classes/component/Component';
import Formio from 'formiojs/Formio';
import _get from 'lodash/get';
import NativePromise from 'native-promise-only';

export default class ReCaptchaComponent extends Component {
  static schema(...extend) {
    return Component.schema(
      {
        type: 'recaptcha',
        key: 'recaptcha',
        label: 'reCAPTCHA',
      },
      ...extend,
    );
  }

  static get builderInfo() {
    return {
      title: 'reCAPTCHA',
      group: 'premium',
      icon: 'refresh',
      documentation: '/userguide/#recaptcha',
      weight: 40,
      schema: ReCaptchaComponent.schema(),
    };
  }

  render() {
    if (this.builderMode) {
      return super.render('reCAPTCHA');
    } else {
      return super.render('', true);
    }
  }

  createInput() {
    if (this.builderMode) {
      // We need to see it in builder mode.
      this.append(this.text(this.name));
    } else {
      const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
      if (siteKey) {
        const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
        this.recaptchaApiReady = Formio.requireLibrary(
          'googleRecaptcha',
          'grecaptcha',
          recaptchaApiScriptUrl,
          true,
        );
      } else {
        console.warn('There is no Site Key specified in settings in form JSON');
      }
    }
  }

  createLabel() {
    return;
  }

  verify(actionName) {
    const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
    if (!siteKey) {
      console.warn('There is no Site Key specified in settings in form JSON');
      return;
    }
    if (!this.recaptchaApiReady) {
      const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${_get(
        this.root.form,
        'settings.recaptcha.siteKey',
      )}`;
      this.recaptchaApiReady = Formio.requireLibrary(
        'googleRecaptcha',
        'grecaptcha',
        recaptchaApiScriptUrl,
        true,
      );
    }
    if (this.recaptchaApiReady) {
      this.recaptchaVerifiedPromise = new NativePromise((resolve, reject) => {
        this.recaptchaApiReady
          .then(() => {
            grecaptcha.ready(() => {
              grecaptcha
                .execute(siteKey, {
                  action: actionName,
                })
                .then((token) => {
                  return this.sendVerificationRequest(token);
                })
                .then(({ verificationResult, token }) => {
                  this.setValue({
                    ...verificationResult,
                    token,
                  });

                  return resolve(verificationResult);
                });
            });
          })
          .catch(() => {
            return reject();
          });
      });
    }
  }

  beforeSubmit() {
    if (this.recaptchaVerifiedPromise) {
      return this.recaptchaVerifiedPromise.then(() => super.beforeSubmit());
    }
    return super.beforeSubmit();
  }

  sendVerificationRequest(token) {
    return Formio.makeStaticRequest(
      `${Formio.projectUrl}/recaptcha?recaptchaToken=${token}`,
    ).then((verificationResult) => ({ verificationResult, token }));
  }

  checkComponentValidity(data, dirty, row, options = {}) {
    data = data || this.rootValue;
    row = row || this.data;
    const { async = false } = options;

    // Verification could be async only
    if (!async) {
      return super.checkComponentValidity(data, dirty, row, options);
    }

    const componentData = row[this.component.key];
    if (!componentData || !componentData.token) {
      this.setCustomValidity('ReCaptcha: Token is not specified in submission');
      return NativePromise.resolve(false);
    }

    return this.hook('validateReCaptcha', componentData.token, () =>
      NativePromise.resolve(true),
    )
      .then((success) => success)
      .catch((err) => {
        this.setCustomValidity(err.message || err);
        return false;
      });
  }

  setValue(value) {
    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    return changed;
  }

  getValue() {
    return this.dataValue;
  }
}
